#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "sthread.h"
#include "SimpleStats.h"
#include "OBLFair_Table.h"

static const int verbose = 0;

struct args{
  OBLFairTable *t;
  SimpleStats *s;
  int myId;
  int enableBliss;
  int amountToFill;
  int thinkSeed;
};

static void *doPhil(void *t);
static void *doFill(void *t);
static void *doClean(void *t);

int
main(int argc, char **argv)
{
  int ii;
  int enableBliss = 0;

  int seed = 0;
  int nPhil = 10;
  sthread_t *phils;
  sthread_t *fill;
  sthread_t *clean;
  struct args *targs;
  struct args *fargs;
  struct args *cargs;
  OBLFairTable table;
  SimpleStats s;

  if(argc > 1){
    seed = atoi(argv[1]);
    printf("seeding with %d\n", seed);
    sutil_srandom(seed);
  }

  if(argc > 2){
    enableBliss = atoi(argv[2]);
  }
  
  OBLF_init(&table, nPhil);
  SS_init(&s, nPhil, verbose, 0);

  phils = (sthread_t *)malloc(nPhil * sizeof(sthread_t));
  assert(phils);
  targs = (struct args *)malloc(nPhil * sizeof(struct args));
  assert(targs);
  for(ii = 0; ii < nPhil; ii++){
    targs[ii].t = &table;
    targs[ii].s = &s;
    targs[ii].myId = ii;
    targs[ii].enableBliss = enableBliss;
    targs[ii].amountToFill = -1;
    targs[ii].thinkSeed = seed;
    sthread_create(&(phils[ii]), (void * (*)(void *))doPhil, (void *)(&(targs[ii])));
  }
  fill = (sthread_t *)malloc(sizeof(sthread_t));
  assert(fill);
  fargs = (struct args *)malloc(sizeof(struct args));
  assert(fargs);
  fargs->t = &table;
  fargs->s = &s;
  fargs->myId = -1;
  fargs->enableBliss = enableBliss;
  fargs->amountToFill = nPhil/2+1;
  fargs->thinkSeed = seed;
  sthread_create(fill, (void * (*)(void *))doFill, (void *)(fargs));

  clean = (sthread_t *)malloc(sizeof(sthread_t));
  assert(clean);
  cargs = (struct args *)malloc(sizeof(struct args));
  assert(cargs);
  cargs->t = &table;
  cargs->s = &s;
  cargs->myId = -1;
  cargs->enableBliss = enableBliss;
  cargs->amountToFill = nPhil/2+1;
  cargs->thinkSeed = seed;
  sthread_create(clean, (void * (*)(void *))doClean, (void *)(cargs));

  for(ii = 0; ii < 10; ii++){
    sthread_sleep(1, 0);
    SS_progress(&s);
    SS_printStats(&s);
  }


  exit(-1);

}

static int
trancendental_bliss()
{
  long r = sutil_random();
  if(r % 10 == 1){
    printf("Ah...\n");
    return 1;
  }
  return 0;
}

static void
longThink()
{
  sthread_sleep(1,0);
}

static void *
doPhil(void *p)
{
  struct args *a = (struct args *)p;
  OBLFairTable *t = a->t;
  SimpleStats *s = a->s;
  int myId = a->myId;
  int thinkSeed = a->thinkSeed;

  printf("Thread %d started\n", myId);

  while(1){
    OBLF_startEating(t, myId);
    SS_eatingStart(s, myId);
    sthread_yield();
    SS_eatingDone(s, myId);
    OBLF_doneEating(t, myId);
    if(thinkSeed){
      long r = sutil_random();
      r = r % 7;
      assert(r < 7);
      int ii;
      for(ii = 0; ii < r; ii++){
        sthread_yield();
      }
    }
    /* Only a few philosophers ever achieve bliss */
    if(myId < 2 && a->enableBliss && trancendental_bliss()){
      longThink();
    }
  }
              
}


static void *
doFill(void *p)
{
  struct args *a = (struct args *)p;
  OBLFairTable *t = a->t;
  SimpleStats *s = a->s;

  printf("Rice server started\n");

  while(1){
    OBLF_takeEmptyBowl(t);
    SS_fillingStart(s);
    sthread_yield();
    SS_fillingDone(s, a->amountToFill);
    OBLF_returnRefilledBowl(t, a->amountToFill);
  }
              
}

typedef void (*ptToFunc)(OBLFairTable *);

static ptToFunc firstG = OBLF_gatherSticks;
static ptToFunc secondG = OBLF_gatherBowl;
static ptToFunc thirdG = OBLF_gatherPlates;

static ptToFunc firstR = OBLF_returnSticks;
static ptToFunc secondR = OBLF_returnBowl;
static ptToFunc thirdR = OBLF_returnPlates;

static void
swapOrder(ptToFunc *one, ptToFunc *two, ptToFunc *three)
{
  ptToFunc tmp;
  long r = sutil_random();
  r = r % 3;
  assert(r < 3);
  if(r == 1){
    tmp = *one;
    *one = *two;
    *two = tmp;
  }
  if(r == 2){
    tmp = *one;
    *one = *three;
    *three = tmp;
  }
  return;
}

static void *
doClean(void *p)
{
  struct args *a = (struct args *)p;
  OBLFairTable *t = a->t;
  SimpleStats *s = a->s;

  printf("Cleaning server started\n");

  while(1){
    OBLF_waitUntilAllPlatesDirty(t);
    if(verbose){
      printf("Cleaner: %s\n",
             firstG == OBLF_gatherSticks ? "gather sticks" : (firstG == OBLF_gatherBowl ? "gather bowl" : "gather plates"));
    }
/*printf("before first call\n");*/
/*printf("t->bowl: %d\n", t->bowl);*/
/*printf("timeToClean(): %d\n", timeToClean(t));*/
/*printf("t->bowl_status: %d\n", t->bowl_status);*/
/*printf("allPlatesDirty(): %d\n", allPlatesDirty(t));*/
    (*firstG)(t);
    if(verbose){
      printf("Cleaner: %s\n",
             secondG == OBLF_gatherSticks ? "gather sticks" : (secondG == OBLF_gatherBowl ? "gather bowl" : "gather plates"));
    }

    SS_cleaningStart(s);
/*printf("before second call\n");*/
/*printf("t->bowl: %d\n", t->bowl);*/
/*printf("timeToClean(): %d\n", timeToClean(t));*/
/*printf("t->bowl_status: %d\n", t->bowl_status);*/
/*printf("allPlatesDirty(): %d\n", allPlatesDirty(t));*/
    (*secondG)(t);
    if(verbose){
      printf("Cleaner: %s\n",
             thirdG == OBLF_gatherSticks ? "gather sticks" : (thirdG == OBLF_gatherBowl ? "gather bowl" : "gather plates"));
    }
    (*thirdG)(t);
    if(verbose){
      printf("Cleaner: %s\n",
             firstR == OBLF_returnSticks ? "return sticks" : (firstR == OBLF_returnBowl ? "return bowl" : "return plates"));
    }
    if(firstR == OBLF_returnBowl){
      SS_cleaningDoneBowl(s);
    }
    (*firstR)(t);
    if(verbose){
      printf("Cleaner: %s\n",
             secondR == OBLF_returnSticks ? "return sticks" : (secondR == OBLF_returnBowl ? "return bowl" : "return plates"));
    }
    if(secondR == OBLF_returnBowl){
      SS_cleaningDoneBowl(s);
    }
    (*secondR)(t);
    if(verbose){
      printf("Cleaner: %s\n",
             thirdR == OBLF_returnSticks ? "return sticks" : (thirdR == OBLF_returnBowl ? "return bowl" : "return plates"));
    }
    if(thirdR == OBLF_returnBowl){
      SS_cleaningDoneBowl(s);
    }
    SS_cleaningDone(s);
    (*thirdR)(t);
    if(a->thinkSeed){
      swapOrder(&firstG, &secondG, &thirdG);
      swapOrder(&firstR, &secondR, &thirdR);
    }
  }
              
}


